#define WIN32_LEAN_AND_MEAN	

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <mp/mpcomplex.h>
#include "test_values.h"
#include "test_functions.h"

using namespace std;

const int DISPLAY_PRECISION = 20;
const int CALCULATE_PRECISION = 50;
char* const buffer = new char[1024];

//arprec formats numbers as 10^exp x coef.
//we need to convert it to coef E exp.
string getvalue( mp_real_temp& value){
	stringstream exp_ss;
	stringstream ss;

	int mpoud = MIN(mp::mpipl-2, MAX(1, mp::mpoud));
	int l;

	mp_real::mpout(value, mpoud, buffer, l, mp::mpnw);
	bool found = false;
	int pos = 4;
	while( true ){
		if( buffer[pos] != ' '){
			break;
		}
		pos++;
	}
	char tmp = buffer[pos];
	while( tmp != ' ' ){
		exp_ss << tmp;
		tmp = buffer[++pos];
	}
	string exp = exp_ss.str();

	pos += 1;
	tmp = buffer[pos];
	while( tmp != ',' ){
		if( tmp != ' ' && tmp != 'x' ){
			ss << tmp;	
		}
		tmp = buffer[++pos];
	}
	if(buffer[pos-1] == '.'){
		ss << "0";
	}

	//sometimes we get non-numeric characters.
	bool isNumber = true;
	for( unsigned int i = 0; i < exp.length(); i++){
		if( !isdigit(exp[i]) && exp[i] != '-'){
			isNumber = false;
			break;
		}
	}
	if( isNumber && exp != "0" ){
		ss << "e" << exp;
	}

	return ss.str();
}

string getvalue( mp_complex_temp& z){
	string ret("new Complex(");
	ret.append( getvalue(z.real));
	ret.append(",");
	ret.append( getvalue(z.imag));
	ret.append(")");
	return ret;
}

string getvalue( mp_complex& z){
	return getvalue(z.toTempAndDestroy());
}

string getvalue( mp_real& value){
	return getvalue(value.toTempAndDestroy());
}

void write_top(string directory, string method, ofstream& os){
	os << "using System;" << endl;
	os << "using NUnit.Framework;" << endl;
	os << "using dnAnalytics.Math;" << endl;
	os << "using dnAnalytics.UnitTests;" << endl;
	os << "namespace dnAnalytics.UnitTests.Math" << endl << "{" << endl;
	os << "\t[TestFixture]" << endl;
	os << "\t[Category(\"Managed\")]" << endl;
	os << "\tpublic class ComplexMath_" << method << "Test" << endl << "\t{" << endl;
	os << "\t\t[Test]"<< endl;
	os << "\t\tpublic void " << method <<"()" << endl << "\t\t{" << endl;
}

void write_bottom(ofstream& os){
	os << "\t\t}" << endl;
	os << "\t}" << endl;
	os << "}" << endl;

	os.flush();
	os.close();
}


void create_test(string directory, string method, double error, mp_real_temp (*func)(const mp_complex&) ){

	ofstream os(directory.append("\\ComplexMath_").append(method).append(".cs").c_str(), ios::out | ios::trunc);
	write_top(directory, method, os);

	for(vector<complex>::iterator it = test_values.begin(); it != test_values.end(); it++){
		mp_real value = func(mp_complex(it->real, it->imag));
		if( abs(value) < DBL_MAX){
			os << "\t\t\t" << "TestHelper.TestRelativeError(ComplexMath." << method << "(";
			os << getvalue(mp_complex(it->real, it->imag)) << "), ";
			os << getvalue(value) << ", " << error << ");" << endl;
		}
	}
	write_bottom(os);
}

void create_test(string directory, string method, double error, mp_complex_temp (*func)(const mp_complex&) ){

	ofstream os(directory.append("\\ComplexMath_").append(method).append(".cs").c_str(), ios::out | ios::trunc);
	write_top(directory, method, os);
	for(vector<complex>::iterator it = test_values.begin(); it != test_values.end(); it++){
		mp_complex z = func(mp_complex(it->real, it->imag));
		if( abs(z.real) < DBL_MAX && abs(z.imag) < DBL_MAX ){
			os << "\t\t\t" << "TestHelper.TestRelativeError(ComplexMath." << method << "(";
			os << getvalue(mp_complex(it->real, it->imag)) << "), ";
			os << getvalue(z) << ", " << error << ");" << endl;
		}
	}
	write_bottom(os);
}


void create_operator_test(string directory, string method, string op, double error, mp_complex_temp (*func)(const mp_complex&, const mp_complex&)){
	ofstream os(directory.append("\\ComplexMath_").append(method).append(".cs").c_str(), ios::out | ios::trunc);
	write_top(directory, method, os);
	os << "\t\t\tComplex a, b; "<< endl;

	for(vector<pair<complex,complex> >::iterator it = test_binary_values.begin(); it != test_binary_values.end(); it++){
		mp_complex z = func(mp_complex(it->first.real, it->first.imag), mp_complex(it->second.real, it->second.imag));
		if( abs(z.real) < DBL_MAX && abs(z.imag) < DBL_MAX ){
			mp_complex a = mp_complex(it->first.real, it->first.imag);
			mp_complex b = mp_complex(it->second.real, it->second.imag);
			os << "\t\t\t" << "a = " << getvalue(a) << ";" << endl ;
			os << "\t\t\t" << "b = " << getvalue(b) << ";" << endl ;

			os << "\t\t\t" << "TestHelper.TestRelativeError(a " << op << " b" << ", ";
			os << getvalue(z) << ", " << error << ");" << endl;
		}
	}
	write_bottom(os);
}


void create_binary_test(string directory, string method, double error, mp_complex_temp (*func)(const mp_complex&, const mp_complex&)){
	ofstream os(directory.append("\\ComplexMath_").append(method).append(".cs").c_str(), ios::out | ios::trunc);
	write_top(directory, method, os);
	os << "\t\t\tComplex a, b; "<< endl;

	for(vector<pair<complex,complex> >::iterator it = test_binary_values.begin(); it != test_binary_values.end(); it++){
		mp_complex z = func(mp_complex(it->first.real, it->first.imag), mp_complex(it->second.real, it->second.imag));
		if( abs(z.real)> DBL_MIN && abs(z.imag) > DBL_MIN && abs(z.real) < DBL_MAX && abs(z.imag) < DBL_MAX ){
			mp_complex a = mp_complex(it->first.real, it->first.imag);
			mp_complex b = mp_complex(it->second.real, it->second.imag);
			os << "\t\t\t" << "a = " << getvalue(a) << ";" << endl ;
			os << "\t\t\t" << "b = " << getvalue(b) << ";" << endl ;

			os << "\t\t\t" << "TestHelper.TestRelativeError(ComplexMath." << method << "(a,b), ";
			os << getvalue(z) << ", " << error << ");" << endl;
		}
	}
	write_bottom(os);
}

int main(int argc, char* argv[])
{
	mp::mp_init(CALCULATE_PRECISION);
	mp::mpsetoutputprec(DISPLAY_PRECISION);

	load_values();
	load_binary_values();
	string dir = ".";
	if( argc > 1){
		dir = argv[1];
	}

	create_test(dir, "Absolute", 2.0E-16, &abs);
	create_test(dir, "Argument", 1.0E-16, &arg);
	create_test(dir, "Sqrt", 2.3E-16, &sqrt);
	create_test(dir, "Exp", 5.0E-14, &exp);
	create_test(dir, "Log", 2.0E-14, &log);
	create_test(dir, "Sin", 5.0E-14, &sin);
	create_test(dir, "Sinh", 5.0E-14, &sinh);
	create_test(dir, "Cos", 5.0E-14, &cos);
	create_test(dir, "Cosh", 5.0E-14, &cosh);
	create_test(dir, "Tan", 5.0E-14, &tan);
	create_test(dir, "Tanh", 5.0E-14, &tanh);
	create_test(dir, "Asin", 1.0E-9, &asin);
	create_test(dir, "Asinh", 1.0E-9, &asinh);
	create_test(dir, "Acos", 1.0E-9, &acos);
	create_test(dir, "Acosh", 2.0E-9, &acosh);
	create_test(dir, "Atan", 1.0E-9, &atan);
	create_test(dir, "Atanh", 1.0E-9, &atanh);

	create_operator_test(dir, "Plus", "+", 1.0E-16, &operator+);
	create_operator_test(dir, "Minus", "-", 1.0E-16, &operator-);
	create_operator_test(dir, "Divide", "/", 1.0E-16, &operator/);
	create_operator_test(dir, "Multiply", "*", 1.0E-16, &operator*);

	create_binary_test(dir, "Pow", 2.0E-9, &power);

	return 0;
}